home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2009 May / maximum-cd-2009-05.iso / DiscContents / Firefox Setup 3.0.6.exe / nonlocalized / components / nsSessionStore.js < prev    next >
Encoding:
JavaScript  |  2009-01-19  |  75.0 KB  |  2,187 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is the nsSessionStore component.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Simon B├╝nzli <zeniko@gmail.com>
  18.  * Portions created by the Initial Developer are Copyright (C) 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Dietrich Ayala <dietrich@mozilla.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. /**
  39.  * Session Storage and Restoration
  40.  * 
  41.  * Overview
  42.  * This service keeps track of a user's session, storing the various bits
  43.  * required to return the browser to it's current state. The relevant data is 
  44.  * stored in memory, and is periodically saved to disk in a file in the 
  45.  * profile directory. The service is started at first window load, in
  46.  * delayedStartup, and will restore the session from the data received from
  47.  * the nsSessionStartup service.
  48.  */
  49.  
  50. /* :::::::: Constants and Helpers ::::::::::::::: */
  51.  
  52. const Cc = Components.classes;
  53. const Ci = Components.interfaces;
  54. const Cr = Components.results;
  55. const Cu = Components.utils;
  56.  
  57. const STATE_STOPPED = 0;
  58. const STATE_RUNNING = 1;
  59. const STATE_QUITTING = -1;
  60. const STATE_DISABLED = -2;
  61.  
  62. const STATE_STOPPED_STR = "stopped";
  63. const STATE_RUNNING_STR = "running";
  64.  
  65. const PRIVACY_NONE = 0;
  66. const PRIVACY_ENCRYPTED = 1;
  67. const PRIVACY_FULL = 2;
  68.  
  69. const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
  70.  
  71. // global notifications observed
  72. const OBSERVING = [
  73.   "domwindowopened", "domwindowclosed",
  74.   "quit-application-requested", "quit-application-granted",
  75.   "quit-application", "browser:purge-session-history"
  76. ];
  77.  
  78. /*
  79. XUL Window properties to (re)store
  80. Restored in restoreDimensions()
  81. */
  82. const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
  83.  
  84. /* 
  85. Hideable window features to (re)store
  86. Restored in restoreWindowFeatures()
  87. */
  88. const WINDOW_HIDEABLE_FEATURES = [
  89.   "menubar", "toolbar", "locationbar", 
  90.   "personalbar", "statusbar", "scrollbars"
  91. ];
  92.  
  93. /*
  94. docShell capabilities to (re)store
  95. Restored in restoreHistory()
  96. eg: browser.docShell["allow" + aCapability] = false;
  97. */
  98. const CAPABILITIES = [
  99.   "Subframes", "Plugins", "Javascript", "MetaRedirects", "Images"
  100. ];
  101.  
  102. // module for JSON conversion (needed for the nsISessionStore API)
  103. Cu.import("resource://gre/modules/JSON.jsm");
  104. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  105.  
  106. function debug(aMsg) {
  107.   aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
  108.   Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService)
  109.                                      .logStringMessage(aMsg);
  110. }
  111.  
  112. /* :::::::: The Service ::::::::::::::: */
  113.  
  114. function SessionStoreService() {
  115. }
  116.  
  117. SessionStoreService.prototype = {
  118.   classDescription: "Browser Session Store Service",
  119.   contractID: "@mozilla.org/browser/sessionstore;1",
  120.   classID: Components.ID("{5280606b-2510-4fe0-97ef-9b5a22eafe6b}"),
  121.   QueryInterface: XPCOMUtils.generateQI([Ci.nsISessionStore,
  122.                                          Ci.nsIDOMEventListener,
  123.                                          Ci.nsIObserver,
  124.                                          Ci.nsISupportsWeakReference]),
  125.  
  126.   // xul:tab attributes to (re)store (extensions might want to hook in here)
  127.   xulAttributes: [],
  128.  
  129.   // set default load state
  130.   _loadState: STATE_STOPPED,
  131.  
  132.   // minimal interval between two save operations (in milliseconds)
  133.   _interval: 10000,
  134.  
  135.   // when crash recovery is disabled, session data is not written to disk
  136.   _resume_from_crash: true,
  137.  
  138.   // During the initial restore tracks the number of windows yet to be restored
  139.   _restoreCount: 0,
  140.  
  141.   // time in milliseconds (Date.now()) when the session was last written to file
  142.   _lastSaveTime: 0, 
  143.  
  144.   // states for all currently opened windows
  145.   _windows: {},
  146.  
  147.   // in case the last closed window ain't a navigator:browser one
  148.   _lastWindowClosed: null,
  149.  
  150.   // not-"dirty" windows usually don't need to have their data updated
  151.   _dirtyWindows: {},
  152.  
  153.   // flag all windows as dirty
  154.   _dirty: false,
  155.  
  156. /* ........ Global Event Handlers .............. */
  157.  
  158.   /**
  159.    * Initialize the component
  160.    */
  161.   init: function sss_init(aWindow) {
  162.     if (this._loadState == STATE_DISABLED)
  163.       return;
  164.  
  165.     if (!aWindow || this._loadState == STATE_RUNNING) {
  166.       // make sure that all browser windows which try to initialize
  167.       // SessionStore are really tracked by it
  168.       if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
  169.         this.onLoad(aWindow);
  170.       return;
  171.     }
  172.  
  173.     this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
  174.                        getService(Ci.nsIPrefService).getBranch("browser.");
  175.     this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  176.  
  177.     var observerService = Cc["@mozilla.org/observer-service;1"].
  178.                           getService(Ci.nsIObserverService);
  179.  
  180.     // if the service is disabled, do not init 
  181.     if (!this._prefBranch.getBoolPref("sessionstore.enabled")) {
  182.       // Notify observers that the sessionstore has done everything it is going to.
  183.       observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
  184.       // Mark as disabled so we don't even try to initialise again.
  185.       this._loadState = STATE_DISABLED;
  186.       return;
  187.     }
  188.  
  189.     OBSERVING.forEach(function(aTopic) {
  190.       observerService.addObserver(this, aTopic, true);
  191.     }, this);
  192.     
  193.     // get interval from prefs - used often, so caching/observing instead of fetching on-demand
  194.     this._interval = this._prefBranch.getIntPref("sessionstore.interval");
  195.     this._prefBranch.addObserver("sessionstore.interval", this, true);
  196.     
  197.     // get crash recovery state from prefs and allow for proper reaction to state changes
  198.     this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
  199.     this._prefBranch.addObserver("sessionstore.resume_from_crash", this, true);
  200.     
  201.     // observe prefs changes so we can modify stored data to match
  202.     this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
  203.  
  204.     // get file references
  205.     var dirService = Cc["@mozilla.org/file/directory_service;1"].
  206.                      getService(Ci.nsIProperties);
  207.     this._sessionFile = dirService.get("ProfD", Ci.nsILocalFile);
  208.     this._sessionFileBackup = this._sessionFile.clone();
  209.     this._sessionFile.append("sessionstore.js");
  210.     this._sessionFileBackup.append("sessionstore.bak");
  211.    
  212.     // get string containing session state
  213.     var iniString;
  214.     try {
  215.       var ss = Cc["@mozilla.org/browser/sessionstartup;1"].
  216.                getService(Ci.nsISessionStartup);
  217.       if (ss.doRestore())
  218.         iniString = ss.state;
  219.     }
  220.     catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok
  221.  
  222.     if (iniString) {
  223.       try {
  224.         // parse the session state into JS objects
  225.         this._initialState = this._safeEval(iniString);
  226.         // set bool detecting crash
  227.         this._lastSessionCrashed =
  228.           this._initialState.session && this._initialState.session.state &&
  229.           this._initialState.session.state == STATE_RUNNING_STR;
  230.         
  231.         // restore the features of the first window from localstore.rdf
  232.         WINDOW_ATTRIBUTES.forEach(function(aAttr) {
  233.           delete this._initialState.windows[0][aAttr];
  234.         }, this);
  235.         delete this._initialState.windows[0].hidden;
  236.       }
  237.       catch (ex) { debug("The session file is invalid: " + ex); }
  238.     }
  239.     
  240.     // if last session crashed, backup the session
  241.     if (this._lastSessionCrashed) {
  242.       try {
  243.         this._writeFile(this._sessionFileBackup, iniString);
  244.       }
  245.       catch (ex) { } // nothing else we can do here
  246.     }
  247.  
  248.     // remove the session data files if crash recovery is disabled
  249.     if (!this._resume_from_crash)
  250.       this._clearDisk();
  251.     
  252.     // As this is called at delayedStartup, restoration must be initiated here
  253.     this.onLoad(aWindow);
  254.   },
  255.  
  256.   /**
  257.    * Called on application shutdown, after notifications:
  258.    * quit-application-granted, quit-application
  259.    */
  260.   _uninit: function sss_uninit() {
  261.     if (this._doResumeSession()) { // save all data for session resuming 
  262.       this.saveState(true);
  263.     }
  264.     else { // discard all session related data 
  265.       this._clearDisk();
  266.     }
  267.     // Make sure to break our cycle with the save timer
  268.     if (this._saveTimer) {
  269.       this._saveTimer.cancel();
  270.       this._saveTimer = null;
  271.     }
  272.   },
  273.  
  274.   /**
  275.    * Handle notifications
  276.    */
  277.   observe: function sss_observe(aSubject, aTopic, aData) {
  278.     // for event listeners
  279.     var _this = this;
  280.  
  281.     switch (aTopic) {
  282.     case "domwindowopened": // catch new windows
  283.       aSubject.addEventListener("load", function(aEvent) {
  284.         aEvent.currentTarget.removeEventListener("load", arguments.callee, false);
  285.         _this.onLoad(aEvent.currentTarget);
  286.         }, false);
  287.       break;
  288.     case "domwindowclosed": // catch closed windows
  289.       this.onClose(aSubject);
  290.       break;
  291.     case "quit-application-requested":
  292.       // get a current snapshot of all windows
  293.       this._forEachBrowserWindow(function(aWindow) {
  294.         this._collectWindowData(aWindow);
  295.       });
  296.       this._dirtyWindows = [];
  297.       this._dirty = false;
  298.       break;
  299.     case "quit-application-granted":
  300.       // freeze the data at what we've got (ignoring closing windows)
  301.       this._loadState = STATE_QUITTING;
  302.       break;
  303.     case "quit-application":
  304.       if (aData == "restart")
  305.         this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
  306.       this._loadState = STATE_QUITTING; // just to be sure
  307.       this._uninit();
  308.       break;
  309.     case "browser:purge-session-history": // catch sanitization 
  310.       this._forEachBrowserWindow(function(aWindow) {
  311.         Array.forEach(aWindow.getBrowser().browsers, function(aBrowser) {
  312.           delete aBrowser.parentNode.__SS_data;
  313.         });
  314.       });
  315.       this._lastWindowClosed = null;
  316.       this._clearDisk();
  317.       // also clear all data about closed tabs
  318.       for (ix in this._windows) {
  319.         this._windows[ix]._closedTabs = [];
  320.       }
  321.       // give the tabbrowsers a chance to clear their histories first
  322.       var win = this._getMostRecentBrowserWindow();
  323.       if (win)
  324.         win.setTimeout(function() { _this.saveState(true); }, 0);
  325.       else
  326.         this.saveState(true);
  327.       break;
  328.     case "nsPref:changed": // catch pref changes
  329.       switch (aData) {
  330.       // if the user decreases the max number of closed tabs they want
  331.       // preserved update our internal states to match that max
  332.       case "sessionstore.max_tabs_undo":
  333.         var ix;
  334.         for (ix in this._windows) {
  335.           this._windows[ix]._closedTabs.splice(this._prefBranch.getIntPref("sessionstore.max_tabs_undo"));
  336.         }
  337.         break;
  338.       case "sessionstore.interval":
  339.         this._interval = this._prefBranch.getIntPref("sessionstore.interval");
  340.         // reset timer and save
  341.         if (this._saveTimer) {
  342.           this._saveTimer.cancel();
  343.           this._saveTimer = null;
  344.         }
  345.         this.saveStateDelayed(null, -1);
  346.         break;
  347.       case "sessionstore.resume_from_crash":
  348.         this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
  349.         // either create the file with crash recovery information or remove it
  350.         // (when _loadState is not STATE_RUNNING, that file is used for session resuming instead)
  351.         if (this._resume_from_crash)
  352.           this.saveState(true);
  353.         else if (this._loadState == STATE_RUNNING)
  354.           this._clearDisk();
  355.         break;
  356.       }
  357.       break;
  358.     case "timer-callback": // timer call back for delayed saving
  359.       this._saveTimer = null;
  360.       this.saveState();
  361.       break;
  362.     }
  363.   },
  364.  
  365. /* ........ Window Event Handlers .............. */
  366.  
  367.   /**
  368.    * Implement nsIDOMEventListener for handling various window and tab events
  369.    */
  370.   handleEvent: function sss_handleEvent(aEvent) {
  371.     switch (aEvent.type) {
  372.       case "load":
  373.       case "pageshow":
  374.         this.onTabLoad(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget, aEvent);
  375.         break;
  376.       case "input":
  377.       case "DOMAutoComplete":
  378.         this.onTabInput(aEvent.currentTarget.ownerDocument.defaultView, aEvent.currentTarget, aEvent);
  379.         break;
  380.       case "TabOpen":
  381.       case "TabClose":
  382.         var panelID = aEvent.originalTarget.linkedPanel;
  383.         var tabpanel = aEvent.originalTarget.ownerDocument.getElementById(panelID);
  384.         if (aEvent.type == "TabOpen") {
  385.           this.onTabAdd(aEvent.currentTarget.ownerDocument.defaultView, tabpanel);
  386.         }
  387.         else {
  388.           this.onTabClose(aEvent.currentTarget.ownerDocument.defaultView, aEvent.originalTarget);
  389.           this.onTabRemove(aEvent.currentTarget.ownerDocument.defaultView, tabpanel);
  390.         }
  391.         break;
  392.       case "TabSelect":
  393.         var tabpanels = aEvent.currentTarget.mPanelContainer;
  394.         this.onTabSelect(aEvent.currentTarget.ownerDocument.defaultView, tabpanels);
  395.         break;
  396.     }
  397.   },
  398.  
  399.   /**
  400.    * If it's the first window load since app start...
  401.    * - determine if we're reloading after a crash or a forced-restart
  402.    * - restore window state
  403.    * - restart downloads
  404.    * Set up event listeners for this window's tabs
  405.    * @param aWindow
  406.    *        Window reference
  407.    */
  408.   onLoad: function sss_onLoad(aWindow) {
  409.     // return if window has already been initialized
  410.     if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
  411.       return;
  412.  
  413.     // ignore non-browser windows and windows opened while shutting down
  414.     if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" ||
  415.       this._loadState == STATE_QUITTING)
  416.       return;
  417.  
  418.     // assign it a unique identifier (timestamp)
  419.     aWindow.__SSi = "window" + Date.now();
  420.  
  421.     // and create its data object
  422.     this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [] };
  423.     
  424.     // perform additional initialization when the first window is loading
  425.     if (this._loadState == STATE_STOPPED) {
  426.       this._loadState = STATE_RUNNING;
  427.       this._lastSaveTime = Date.now();
  428.       
  429.       // don't save during the first ten seconds
  430.       // (until most of the pages have been restored)
  431.       this.saveStateDelayed(aWindow, 10000);
  432.  
  433.       // restore a crashed session resp. resume the last session if requested
  434.       if (this._initialState) {
  435.         // make sure that the restored tabs are first in the window
  436.         this._initialState._firstTabs = true;
  437.         this._restoreCount = this._initialState.windows ? this._initialState.windows.length : 0;
  438.         this.restoreWindow(aWindow, this._initialState, this._isCmdLineEmpty(aWindow));
  439.         delete this._initialState;
  440.       }
  441.       else {
  442.         // Nothing to restore, notify observers things are complete.
  443.         var observerService = Cc["@mozilla.org/observer-service;1"].
  444.                               getService(Ci.nsIObserverService);
  445.         observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
  446.       }
  447.     }
  448.     
  449.     var tabbrowser = aWindow.getBrowser();
  450.     var tabpanels = tabbrowser.mPanelContainer;
  451.     
  452.     // add tab change listeners to all already existing tabs
  453.     for (var i = 0; i < tabpanels.childNodes.length; i++) {
  454.       this.onTabAdd(aWindow, tabpanels.childNodes[i], true);
  455.     }
  456.     // notification of tab add/remove/selection
  457.     tabbrowser.addEventListener("TabOpen", this, true);
  458.     tabbrowser.addEventListener("TabClose", this, true);
  459.     tabbrowser.addEventListener("TabSelect", this, true);
  460.   },
  461.  
  462.   /**
  463.    * On window close...
  464.    * - remove event listeners from tabs
  465.    * - save all window data
  466.    * @param aWindow
  467.    *        Window reference
  468.    */
  469.   onClose: function sss_onClose(aWindow) {
  470.     // ignore windows not tracked by SessionStore
  471.     if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
  472.       return;
  473.     }
  474.     
  475.     if (this.windowToFocus && this.windowToFocus == aWindow) {
  476.       delete this.windowToFocus;
  477.     }
  478.     
  479.     var tabbrowser = aWindow.getBrowser();
  480.     var tabpanels = tabbrowser.mPanelContainer;
  481.  
  482.     tabbrowser.removeEventListener("TabOpen", this, true);
  483.     tabbrowser.removeEventListener("TabClose", this, true);
  484.     tabbrowser.removeEventListener("TabSelect", this, true);
  485.     
  486.     if (this._loadState == STATE_RUNNING) { // window not closed during a regular shut-down 
  487.       // update all window data for a last time
  488.       this._collectWindowData(aWindow);
  489.       
  490.       // preserve this window's data (in case it was the last navigator:browser)
  491.       this._lastWindowClosed = this._windows[aWindow.__SSi];
  492.       this._lastWindowClosed.title = aWindow.content.document.title;
  493.       this._updateCookies([this._lastWindowClosed]);
  494.       
  495.       // clear this window from the list
  496.       delete this._windows[aWindow.__SSi];
  497.       
  498.       // save the state without this window to disk
  499.       this.saveStateDelayed();
  500.     }
  501.     
  502.     for (var i = 0; i < tabpanels.childNodes.length; i++) {
  503.       this.onTabRemove(aWindow, tabpanels.childNodes[i], true);
  504.     }
  505.     
  506.     // cache the window state until the window is completely gone
  507.     aWindow.__SS_dyingCache = this._windows[aWindow.__SSi] || this._lastWindowClosed;
  508.     
  509.     // reset the _tab property to avoid keeping the tab's XUL element alive
  510.     // longer than we need it
  511.     var tabCount = aWindow.__SS_dyingCache.tabs.length;
  512.     for (var t = 0; t < tabCount; t++) {
  513.       delete aWindow.__SS_dyingCache.tabs[t]._tab;
  514.     }
  515.     
  516.     delete aWindow.__SSi;
  517.   },
  518.  
  519.   /**
  520.    * set up listeners for a new tab
  521.    * @param aWindow
  522.    *        Window reference
  523.    * @param aPanel
  524.    *        TabPanel reference
  525.    * @param aNoNotification
  526.    *        bool Do not save state if we're updating an existing tab
  527.    */
  528.   onTabAdd: function sss_onTabAdd(aWindow, aPanel, aNoNotification) {
  529.     aPanel.addEventListener("load", this, true);
  530.     aPanel.addEventListener("pageshow", this, true);
  531.     aPanel.addEventListener("input", this, true);
  532.     aPanel.addEventListener("DOMAutoComplete", this, true);
  533.     
  534.     if (!aNoNotification) {
  535.       this.saveStateDelayed(aWindow);
  536.     }
  537.   },
  538.  
  539.   /**
  540.    * remove listeners for a tab
  541.    * @param aWindow
  542.    *        Window reference
  543.    * @param aPanel
  544.    *        TabPanel reference
  545.    * @param aNoNotification
  546.    *        bool Do not save state if we're updating an existing tab
  547.    */
  548.   onTabRemove: function sss_onTabRemove(aWindow, aPanel, aNoNotification) {
  549.     aPanel.removeEventListener("load", this, true);
  550.     aPanel.removeEventListener("pageshow", this, true);
  551.     aPanel.removeEventListener("input", this, true);
  552.     aPanel.removeEventListener("DOMAutoComplete", this, true);
  553.     
  554.     delete aPanel.__SS_data;
  555.     delete aPanel.__SS_text;
  556.     
  557.     if (!aNoNotification) {
  558.       this.saveStateDelayed(aWindow);
  559.     }
  560.   },
  561.  
  562.   /**
  563.    * When a tab closes, collect it's properties
  564.    * @param aWindow
  565.    *        Window reference
  566.    * @param aTab
  567.    *        TabPanel reference
  568.    */
  569.   onTabClose: function sss_onTabClose(aWindow, aTab) {
  570.     // notify the tabbrowser that the tab state will be retrieved for the last time
  571.     // (so that extension authors can easily set data on soon-to-be-closed tabs)
  572.     var event = aWindow.document.createEvent("Events");
  573.     event.initEvent("SSTabClosing", true, false);
  574.     aTab.dispatchEvent(event);
  575.     
  576.     var maxTabsUndo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
  577.     // don't update our internal state if we don't have to
  578.     if (maxTabsUndo == 0) {
  579.       return;
  580.     }
  581.     
  582.     // make sure that the tab related data is up-to-date
  583.     var tabState = this._collectTabData(aTab);
  584.     this._updateTextAndScrollDataForTab(aWindow, aTab.linkedBrowser, tabState);
  585.  
  586.     // reset the _tab property to avoid keeping the tab's XUL element alive
  587.     // longer than we need it
  588.     delete tabState._tab;
  589.     
  590.     // store closed-tab data for undo
  591.     if (tabState.entries.length > 1 || tabState.entries[0].url != "about:blank") {
  592.       this._windows[aWindow.__SSi]._closedTabs.unshift({
  593.         state: tabState,
  594.         title: aTab.getAttribute("label"),
  595.         image: aTab.getAttribute("image"),
  596.         pos: aTab._tPos
  597.       });
  598.       var length = this._windows[aWindow.__SSi]._closedTabs.length;
  599.       if (length > maxTabsUndo)
  600.         this._windows[aWindow.__SSi]._closedTabs.splice(maxTabsUndo, length - maxTabsUndo);
  601.     }
  602.   },
  603.  
  604.   /**
  605.    * When a tab loads, save state.
  606.    * @param aWindow
  607.    *        Window reference
  608.    * @param aPanel
  609.    *        TabPanel reference
  610.    * @param aEvent
  611.    *        Event obj
  612.    */
  613.   onTabLoad: function sss_onTabLoad(aWindow, aPanel, aEvent) { 
  614.     // react on "load" and solitary "pageshow" events (the first "pageshow"
  615.     // following "load" is too late for deleting the data caches)
  616.     if (aEvent.type != "load" && !aEvent.persisted) {
  617.       return;
  618.     }
  619.     
  620.     delete aPanel.__SS_data;
  621.     delete aPanel.__SS_text;
  622.     this.saveStateDelayed(aWindow);
  623.     
  624.     // attempt to update the current URL we send in a crash report
  625.     this._updateCrashReportURL(aWindow);
  626.   },
  627.  
  628.   /**
  629.    * Called when a tabpanel sends the "input" notification 
  630.    * stores textarea data
  631.    * @param aWindow
  632.    *        Window reference
  633.    * @param aPanel
  634.    *        TabPanel reference
  635.    * @param aEvent
  636.    *        Event obj
  637.    */
  638.   onTabInput: function sss_onTabInput(aWindow, aPanel, aEvent) {
  639.     if (this._saveTextData(aPanel, aEvent.originalTarget)) {
  640.       this.saveStateDelayed(aWindow, 3000);
  641.     }
  642.   },
  643.  
  644.   /**
  645.    * When a tab is selected, save session data
  646.    * @param aWindow
  647.    *        Window reference
  648.    * @param aPanels
  649.    *        TabPanel reference
  650.    */
  651.   onTabSelect: function sss_onTabSelect(aWindow, aPanels) {
  652.     if (this._loadState == STATE_RUNNING) {
  653.       this._windows[aWindow.__SSi].selected = aPanels.selectedIndex;
  654.       this.saveStateDelayed(aWindow);
  655.  
  656.       // attempt to update the current URL we send in a crash report
  657.       this._updateCrashReportURL(aWindow);
  658.     }
  659.   },
  660.  
  661. /* ........ nsISessionStore API .............. */
  662.  
  663.   getBrowserState: function sss_getBrowserState() {
  664.     return this._toJSONString(this._getCurrentState());
  665.   },
  666.  
  667.   setBrowserState: function sss_setBrowserState(aState) {
  668.     var window = this._getMostRecentBrowserWindow();
  669.     if (!window) {
  670.       this._openWindowWithState("(" + aState + ")");
  671.       return;
  672.     }
  673.  
  674.     // close all other browser windows
  675.     this._forEachBrowserWindow(function(aWindow) {
  676.       if (aWindow != window) {
  677.         aWindow.close();
  678.       }
  679.     });
  680.  
  681.     // restore to the given state
  682.     this.restoreWindow(window, "(" + aState + ")", true);
  683.   },
  684.  
  685.   getWindowState: function sss_getWindowState(aWindow) {
  686.     if (!aWindow.__SSi && aWindow.__SS_dyingCache)
  687.       return this._toJSONString({ windows: [aWindow.__SS_dyingCache] });
  688.     
  689.     return this._toJSONString(this._getWindowState(aWindow));
  690.   },
  691.  
  692.   setWindowState: function sss_setWindowState(aWindow, aState, aOverwrite) {
  693.     this.restoreWindow(aWindow, "(" + aState + ")", aOverwrite);
  694.   },
  695.  
  696.   getTabState: function sss_getTabState(aTab) {
  697.     var tabState = this._collectTabData(aTab);
  698.     
  699.     var window = aTab.ownerDocument.defaultView;
  700.     this._updateTextAndScrollDataForTab(window, aTab.linkedBrowser, tabState);
  701.     
  702.     return this._toJSONString(tabState);
  703.   },
  704.  
  705.   setTabState: function sss_setTabState(aTab, aState) {
  706.     var tabState = this._safeEval("(" + aState + ")");
  707.     if (!tabState.entries || !tabState.entries.length) {
  708.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  709.       return;
  710.     }
  711.     tabState._tab = aTab;
  712.     
  713.     var window = aTab.ownerDocument.defaultView;
  714.     this.restoreHistoryPrecursor(window, [tabState], 0, 0, 0);
  715.   },
  716.  
  717.   duplicateTab: function sss_duplicateTab(aWindow, aTab) {
  718.     var tabState = this._collectTabData(aTab, true);
  719.     var sourceWindow = aTab.ownerDocument.defaultView;
  720.     this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true);
  721.     
  722.     var newTab = aWindow.getBrowser().addTab();
  723.     tabState._tab = newTab;
  724.     this.restoreHistoryPrecursor(aWindow, [tabState], 0, 0, 0);
  725.     
  726.     return newTab;
  727.   },
  728.  
  729.   getClosedTabCount: function sss_getClosedTabCount(aWindow) {
  730.     if (!aWindow.__SSi && aWindow.__SS_dyingCache)
  731.       return aWindow.__SS_dyingCache._closedTabs.length;
  732.     if (!aWindow.__SSi)
  733.       return 0; // not a browser window, or not otherwise tracked by SS.
  734.     
  735.     return this._windows[aWindow.__SSi]._closedTabs.length;
  736.   },
  737.  
  738.   closedTabNameAt: function sss_closedTabNameAt(aWindow, aIx) {
  739.     var tabs;
  740.     
  741.     if (aWindow.__SSi && aWindow.__SSi in this._windows)
  742.       tabs = this._windows[aWindow.__SSi]._closedTabs;
  743.     else if (aWindow.__SS_dyingCache)
  744.       tabs = aWindow.__SS_dyingCache._closedTabs;
  745.     else
  746.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  747.     
  748.     return tabs && aIx in tabs ? tabs[aIx].title : null;
  749.   },
  750.  
  751.   getClosedTabData: function sss_getClosedTabDataAt(aWindow) {
  752.     if (!aWindow.__SSi && aWindow.__SS_dyingCache)
  753.       return this._toJSONString(aWindow.__SS_dyingCache._closedTabs);
  754.     
  755.     return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
  756.   },
  757.  
  758.   undoCloseTab: function sss_undoCloseTab(aWindow, aIndex) {
  759.     var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
  760.  
  761.     // default to the most-recently closed tab
  762.     aIndex = aIndex || 0;
  763.  
  764.     if (aIndex in closedTabs) {
  765.       var browser = aWindow.getBrowser();
  766.  
  767.       // fetch the data of closed tab, while removing it from the array
  768.       var closedTab = closedTabs.splice(aIndex, 1).shift();
  769.       var closedTabState = closedTab.state;
  770.  
  771.       // create a new tab
  772.       closedTabState._tab = browser.addTab();
  773.         
  774.       // restore the tab's position
  775.       browser.moveTabTo(closedTabState._tab, closedTab.pos);
  776.   
  777.       // restore tab content
  778.       this.restoreHistoryPrecursor(aWindow, [closedTabState], 1, 0, 0);
  779.  
  780.       // focus the tab's content area
  781.       var content = browser.getBrowserForTab(closedTabState._tab).contentWindow;
  782.       aWindow.setTimeout(function() { content.focus(); }, 0);
  783.     }
  784.     else {
  785.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  786.     }
  787.   },
  788.  
  789.   getWindowValue: function sss_getWindowValue(aWindow, aKey) {
  790.     if (aWindow.__SSi) {
  791.       var data = this._windows[aWindow.__SSi].extData || {};
  792.       return data[aKey] || "";
  793.     }
  794.     else if (aWindow.__SS_dyingCache) {
  795.       data = aWindow.__SS_dyingCache.extData || {};
  796.       return data[aKey] || "";
  797.     }
  798.     else {
  799.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  800.     }
  801.   },
  802.  
  803.   setWindowValue: function sss_setWindowValue(aWindow, aKey, aStringValue) {
  804.     if (aWindow.__SSi) {
  805.       if (!this._windows[aWindow.__SSi].extData) {
  806.         this._windows[aWindow.__SSi].extData = {};
  807.       }
  808.       this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
  809.       this.saveStateDelayed(aWindow);
  810.     }
  811.     else {
  812.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  813.     }
  814.   },
  815.  
  816.   deleteWindowValue: function sss_deleteWindowValue(aWindow, aKey) {
  817.     if (this._windows[aWindow.__SSi].extData[aKey])
  818.       delete this._windows[aWindow.__SSi].extData[aKey];
  819.     else
  820.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  821.   },
  822.  
  823.   getTabValue: function sss_getTabValue(aTab, aKey) {
  824.     var data = aTab.__SS_extdata || {};
  825.     return data[aKey] || "";
  826.   },
  827.  
  828.   setTabValue: function sss_setTabValue(aTab, aKey, aStringValue) {
  829.     if (!aTab.__SS_extdata) {
  830.       aTab.__SS_extdata = {};
  831.     }
  832.     aTab.__SS_extdata[aKey] = aStringValue;
  833.     this.saveStateDelayed(aTab.ownerDocument.defaultView);
  834.   },
  835.  
  836.   deleteTabValue: function sss_deleteTabValue(aTab, aKey) {
  837.     if (aTab.__SS_extdata[aKey])
  838.       delete aTab.__SS_extdata[aKey];
  839.     else
  840.       Components.returnCode = Cr.NS_ERROR_INVALID_ARG;
  841.   },
  842.  
  843.  
  844.   persistTabAttribute: function sss_persistTabAttribute(aName) {
  845.     this.xulAttributes.push(aName);
  846.     this.saveStateDelayed();
  847.   },
  848.  
  849. /* ........ Saving Functionality .............. */
  850.  
  851.   /**
  852.    * Store all session data for a window
  853.    * @param aWindow
  854.    *        Window reference
  855.    */
  856.   _saveWindowHistory: function sss_saveWindowHistory(aWindow) {
  857.     var tabbrowser = aWindow.getBrowser();
  858.     var tabs = tabbrowser.mTabs;
  859.     var tabsData = this._windows[aWindow.__SSi].tabs = [];
  860.     
  861.     for (var i = 0; i < tabs.length; i++)
  862.       tabsData.push(this._collectTabData(tabs[i]));
  863.     
  864.     this._windows[aWindow.__SSi].selected = tabbrowser.mTabBox.selectedIndex + 1;
  865.   },
  866.  
  867.   /**
  868.    * Collect data related to a single tab
  869.    * @param aTab
  870.    *        tabbrowser tab
  871.    * @param aFullData
  872.    *        always return privacy sensitive data (use with care)
  873.    * @returns object
  874.    */
  875.   _collectTabData: function sss_collectTabData(aTab, aFullData) {
  876.     var tabData = { entries: [], index: 0 };
  877.     var browser = aTab.linkedBrowser;
  878.     
  879.     if (!browser || !browser.currentURI)
  880.       // can happen when calling this function right after .addTab()
  881.       return tabData;
  882.     else if (browser.parentNode.__SS_data && browser.parentNode.__SS_data._tab)
  883.       // use the data to be restored when the tab hasn't been completely loaded
  884.       return browser.parentNode.__SS_data;
  885.     
  886.     var history = null;
  887.     try {
  888.       history = browser.sessionHistory;
  889.     }
  890.     catch (ex) { } // this could happen if we catch a tab during (de)initialization
  891.     
  892.     if (history && browser.parentNode.__SS_data &&
  893.         browser.parentNode.__SS_data.entries[history.index] && !aFullData) {
  894.       tabData = browser.parentNode.__SS_data;
  895.       tabData.index = history.index + 1;
  896.     }
  897.     else if (history && history.count > 0) {
  898.       for (var j = 0; j < history.count; j++)
  899.         tabData.entries.push(this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
  900.                                                          aFullData));
  901.       tabData.index = history.index + 1;
  902.  
  903.       // make sure not to cache privacy sensitive data which shouldn't get out
  904.       if (!aFullData)
  905.         browser.parentNode.__SS_data = tabData;
  906.     }
  907.     else {
  908.       tabData.entries[0] = { url: browser.currentURI.spec };
  909.       tabData.index = 1;
  910.     }
  911.     
  912.     var disallow = [];
  913.     for (var i = 0; i < CAPABILITIES.length; i++)
  914.       if (!browser.docShell["allow" + CAPABILITIES[i]])
  915.         disallow.push(CAPABILITIES[i]);
  916.     if (disallow.length > 0)
  917.       tabData.disallow = disallow.join(",");
  918.     else if (tabData.disallow)
  919.       delete tabData.disallow;
  920.     
  921.     if (this.xulAttributes.length > 0) {
  922.       var xulattr = Array.filter(aTab.attributes, function(aAttr) {
  923.         return this.xulAttributes.indexOf(aAttr.name) > -1;
  924.       }, this).map(function(aAttr) {
  925.         return aAttr.name + "=" + encodeURI(aAttr.value);
  926.       });
  927.       tabData.xultab = xulattr.join(" ");
  928.     }
  929.     
  930.     if (aTab.__SS_extdata)
  931.       tabData.extData = aTab.__SS_extdata;
  932.     else if (tabData.extData)
  933.       delete tabData.extData;
  934.     
  935.     return tabData;
  936.   },
  937.  
  938.   /**
  939.    * Get an object that is a serialized representation of a History entry
  940.    * Used for data storage
  941.    * @param aEntry
  942.    *        nsISHEntry instance
  943.    * @param aFullData
  944.    *        always return privacy sensitive data (use with care)
  945.    * @returns object
  946.    */
  947.   _serializeHistoryEntry: function sss_serializeHistoryEntry(aEntry, aFullData) {
  948.     var entry = { url: aEntry.URI.spec };
  949.     
  950.     if (aEntry.title && aEntry.title != entry.url) {
  951.       entry.title = aEntry.title;
  952.     }
  953.     if (aEntry.isSubFrame) {
  954.       entry.subframe = true;
  955.     }
  956.     if (!(aEntry instanceof Ci.nsISHEntry)) {
  957.       return entry;
  958.     }
  959.     
  960.     var cacheKey = aEntry.cacheKey;
  961.     if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
  962.         cacheKey.data != 0) {
  963.       // XXXbz would be better to have cache keys implement
  964.       // nsISerializable or something.
  965.       entry.cacheKey = cacheKey.data;
  966.     }
  967.     entry.ID = aEntry.ID;
  968.     
  969.     if (aEntry.contentType)
  970.       entry.contentType = aEntry.contentType;
  971.     
  972.     var x = {}, y = {};
  973.     aEntry.getScrollPosition(x, y);
  974.     if (x.value != 0 || y.value != 0)
  975.       entry.scroll = x.value + "," + y.value;
  976.     
  977.     try {
  978.       var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
  979.       if (aEntry.postData && (aFullData ||
  980.             prefPostdata && this._checkPrivacyLevel(aEntry.URI.schemeIs("https")))) {
  981.         aEntry.postData.QueryInterface(Ci.nsISeekableStream).
  982.                         seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
  983.         var stream = Cc["@mozilla.org/binaryinputstream;1"].
  984.                      createInstance(Ci.nsIBinaryInputStream);
  985.         stream.setInputStream(aEntry.postData);
  986.         var postBytes = stream.readByteArray(stream.available());
  987.         var postdata = String.fromCharCode.apply(null, postBytes);
  988.         if (aFullData || prefPostdata == -1 ||
  989.             postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
  990.               prefPostdata) {
  991.           // We can stop doing base64 encoding once our serialization into JSON
  992.           // is guaranteed to handle all chars in strings, including embedded
  993.           // nulls.
  994.           entry.postdata_b64 = btoa(postdata);
  995.         }
  996.       }
  997.     }
  998.     catch (ex) { debug(ex); } // POSTDATA is tricky - especially since some extensions don't get it right
  999.  
  1000.     if (aEntry.owner) {
  1001.       // Not catching anything specific here, just possible errors
  1002.       // from writeCompoundObject and the like.
  1003.       try {
  1004.         var binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
  1005.                            createInstance(Ci.nsIObjectOutputStream);
  1006.         var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
  1007.         pipe.init(false, false, 0, 0xffffffff, null);
  1008.         binaryStream.setOutputStream(pipe.outputStream);
  1009.         binaryStream.writeCompoundObject(aEntry.owner, Ci.nsISupports, true);
  1010.         binaryStream.close();
  1011.  
  1012.         // Now we want to read the data from the pipe's input end and encode it.
  1013.         var scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].
  1014.                                createInstance(Ci.nsIBinaryInputStream);
  1015.         scriptableStream.setInputStream(pipe.inputStream);
  1016.         var ownerBytes =
  1017.           scriptableStream.readByteArray(scriptableStream.available());
  1018.         // We can stop doing base64 encoding once our serialization into JSON
  1019.         // is guaranteed to handle all chars in strings, including embedded
  1020.         // nulls.
  1021.         entry.owner_b64 = btoa(String.fromCharCode.apply(null, ownerBytes));
  1022.       }
  1023.       catch (ex) { debug(ex); }
  1024.     }
  1025.     
  1026.     if (!(aEntry instanceof Ci.nsISHContainer)) {
  1027.       return entry;
  1028.     }
  1029.     
  1030.     if (aEntry.childCount > 0) {
  1031.       entry.children = [];
  1032.       for (var i = 0; i < aEntry.childCount; i++) {
  1033.         var child = aEntry.GetChildAt(i);
  1034.         if (child) {
  1035.           entry.children.push(this._serializeHistoryEntry(child, aFullData));
  1036.         }
  1037.         else { // to maintain the correct frame order, insert a dummy entry 
  1038.           entry.children.push({ url: "about:blank" });
  1039.         }
  1040.       }
  1041.     }
  1042.     
  1043.     return entry;
  1044.   },
  1045.  
  1046.   /**
  1047.    * Updates the current document's cache of user entered text data
  1048.    * @param aPanel
  1049.    *        TabPanel reference
  1050.    * @param aTextarea
  1051.    *        HTML content element
  1052.    * @returns bool
  1053.    */
  1054.   _saveTextData: function sss_saveTextData(aPanel, aTextarea) {
  1055.     var id = aTextarea.id ? "#" + aTextarea.id :
  1056.                             aTextarea.name;
  1057.     if (!id
  1058.       || !(aTextarea instanceof Ci.nsIDOMHTMLTextAreaElement 
  1059.       || aTextarea instanceof Ci.nsIDOMHTMLInputElement &&
  1060.          aTextarea.type != "password" && aTextarea.type != "file")) {
  1061.       return false; // nothing to save
  1062.     }
  1063.     if (/^(?:\d+\|)+/.test(id)) {
  1064.       // text could be restored into a subframe, so skip it (see bug 463206)
  1065.       return false;
  1066.     }
  1067.     
  1068.     if (!aPanel.__SS_text) {
  1069.       aPanel.__SS_text = [];
  1070.       aPanel.__SS_text._refs = [];
  1071.     }
  1072.     
  1073.     // get the index of the reference to the text element
  1074.     var ix = aPanel.__SS_text._refs.indexOf(aTextarea);
  1075.     if (ix == -1) {
  1076.       // we haven't registered this text element yet - do so now
  1077.       aPanel.__SS_text._refs.push(aTextarea);
  1078.       ix = aPanel.__SS_text.length;
  1079.     }
  1080.     else if (!aPanel.__SS_text[ix].cache) {
  1081.       // we've already marked this text element for saving (the cache is
  1082.       // added during save operations and would have to be updated here)
  1083.       return false;
  1084.     }
  1085.     
  1086.     // determine the frame we're in and encode it into the textarea's ID
  1087.     var content = aTextarea.ownerDocument.defaultView;
  1088.     while (content != content.top) {
  1089.       var frames = content.parent.frames;
  1090.       for (var i = 0; i < frames.length && frames[i] != content; i++);
  1091.       id = i + "|" + id;
  1092.       content = content.parent;
  1093.     }
  1094.     
  1095.     // mark this element for saving
  1096.     aPanel.__SS_text[ix] = { id: id, element: aTextarea };
  1097.     
  1098.     return true;
  1099.   },
  1100.  
  1101.   /**
  1102.    * go through all tabs and store the current scroll positions
  1103.    * and innerHTML content of WYSIWYG editors
  1104.    * @param aWindow
  1105.    *        Window reference
  1106.    */
  1107.   _updateTextAndScrollData: function sss_updateTextAndScrollData(aWindow) {
  1108.     var browsers = aWindow.getBrowser().browsers;
  1109.     for (var i = 0; i < browsers.length; i++) {
  1110.       try {
  1111.         var tabData = this._windows[aWindow.__SSi].tabs[i];
  1112.         if (tabData.entries.length == 0 ||
  1113.             browsers[i].parentNode.__SS_data && browsers[i].parentNode.__SS_data._tab)
  1114.           continue; // ignore incompletely initialized tabs
  1115.         this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
  1116.       }
  1117.       catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time)
  1118.     }
  1119.   },
  1120.  
  1121.   /**
  1122.    * go through all frames and store the current scroll positions
  1123.    * and innerHTML content of WYSIWYG editors
  1124.    * @param aWindow
  1125.    *        Window reference
  1126.    * @param aBrowser
  1127.    *        single browser reference
  1128.    * @param aTabData
  1129.    *        tabData object to add the information to
  1130.    * @param aFullData
  1131.    *        always return privacy sensitive data (use with care)
  1132.    */
  1133.   _updateTextAndScrollDataForTab:
  1134.     function sss_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) {
  1135.     var text = [];
  1136.     if (aBrowser.parentNode.__SS_text &&
  1137.         (aFullData || this._checkPrivacyLevel(aBrowser.currentURI.schemeIs("https")))) {
  1138.       for (var ix = aBrowser.parentNode.__SS_text.length - 1; ix >= 0; ix--) {
  1139.         var data = aBrowser.parentNode.__SS_text[ix];
  1140.         if (!data.cache)
  1141.           // update the text element's value before adding it to the data structure
  1142.           data.cache = encodeURI(data.element.value);
  1143.         text.push(data.id + "=" + data.cache);
  1144.       }
  1145.     }
  1146.     if (aBrowser.currentURI.spec == "about:config")
  1147.       text = ["#textbox=" + encodeURI(aBrowser.contentDocument.getElementById("textbox").
  1148.                                                wrappedJSObject.value)];
  1149.     if (text.length > 0)
  1150.       aTabData.text = text.join(" ");
  1151.     else if (aTabData.text)
  1152.       delete aTabData.text;
  1153.     
  1154.     var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
  1155.     // entry data needn't exist for tabs just initialized with an incomplete session state
  1156.     if (aTabData.entries[tabIndex])
  1157.       this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
  1158.                                             aTabData.entries[tabIndex], aFullData);
  1159.   },
  1160.  
  1161.   /**
  1162.    * go through all subframes and store the current scroll positions
  1163.    * and innerHTML content of WYSIWYG editors
  1164.    * @param aWindow
  1165.    *        Window reference
  1166.    * @param aContent
  1167.    *        frame reference
  1168.    * @param aData
  1169.    *        part of a tabData object to add the information to
  1170.    * @param aFullData
  1171.    *        always return privacy sensitive data (use with care)
  1172.    */
  1173.   _updateTextAndScrollDataForFrame:
  1174.     function sss_updateTextAndScrollDataForFrame(aWindow, aContent, aData, aFullData) {
  1175.     for (var i = 0; i < aContent.frames.length; i++) {
  1176.       if (aData.children && aData.children[i])
  1177.         this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i], aData.children[i], aFullData);
  1178.     }
  1179.     // designMode is undefined e.g. for XUL documents (as about:config)
  1180.     var isHTTPS = this._getURIFromString((aContent.parent || aContent).
  1181.                                          document.location.href).schemeIs("https");
  1182.     if ((aContent.document.designMode || "") == "on" &&
  1183.         (aFullData || this._checkPrivacyLevel(isHTTPS))) {
  1184.       if (aData.innerHTML === undefined && !aFullData) {
  1185.         // we get no "input" events from iframes - listen for keypress here
  1186.         var _this = this;
  1187.         aContent.addEventListener("keypress", function(aEvent) {
  1188.           _this.saveStateDelayed(aWindow, 3000); }, true);
  1189.       }
  1190.       aData.innerHTML = aContent.document.body.innerHTML;
  1191.     }
  1192.     aData.scroll = aContent.scrollX + "," + aContent.scrollY;
  1193.    },
  1194.  
  1195.   /**
  1196.    * store all hosts for a URL
  1197.    * @param aWindow
  1198.    *        Window reference
  1199.    */
  1200.   _updateCookieHosts: function sss_updateCookieHosts(aWindow) {
  1201.     var hosts = this._windows[aWindow.__SSi]._hosts = {};
  1202.     
  1203.     // get all possible subdomain levels for a given URL
  1204.     var _this = this;
  1205.     function extractHosts(aEntry) {
  1206.       if (/^https?:\/\/(?:[^@\/\s]+@)?([\w.-]+)/.test(aEntry.url) &&
  1207.         !hosts[RegExp.$1] && _this._checkPrivacyLevel(_this._getURIFromString(aEntry.url).schemeIs("https"))) {
  1208.         var host = RegExp.$1;
  1209.         var ix;
  1210.         for (ix = host.indexOf(".") + 1; ix; ix = host.indexOf(".", ix) + 1) {
  1211.           hosts[host.substr(ix)] = true;
  1212.         }
  1213.         hosts[host] = true;
  1214.       }
  1215.       else if (/^file:\/\/([^\/]*)/.test(aEntry.url)) {
  1216.         hosts[RegExp.$1] = true;
  1217.       }
  1218.       if (aEntry.children) {
  1219.         aEntry.children.forEach(extractHosts);
  1220.       }
  1221.     }
  1222.     
  1223.     this._windows[aWindow.__SSi].tabs.forEach(function(aTabData) { aTabData.entries.forEach(extractHosts); });
  1224.   },
  1225.  
  1226.   /**
  1227.    * Serialize cookie data
  1228.    * @param aWindows
  1229.    *        array of Window references
  1230.    */
  1231.   _updateCookies: function sss_updateCookies(aWindows) {
  1232.     var cookiesEnum = Cc["@mozilla.org/cookiemanager;1"].
  1233.                       getService(Ci.nsICookieManager).enumerator;
  1234.     // collect the cookies per window
  1235.     for (var i = 0; i < aWindows.length; i++)
  1236.       aWindows[i].cookies = [];
  1237.     
  1238.     // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
  1239.     var MAX_EXPIRY = Math.pow(2, 62);
  1240.     while (cookiesEnum.hasMoreElements()) {
  1241.       var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
  1242.       if (cookie.isSession && this._checkPrivacyLevel(cookie.isSecure)) {
  1243.         var jscookie = null;
  1244.         aWindows.forEach(function(aWindow) {
  1245.           if (aWindow._hosts && aWindow._hosts[cookie.rawHost]) {
  1246.             // serialize the cookie when it's first needed
  1247.             if (!jscookie) {
  1248.               jscookie = { host: cookie.host, value: cookie.value };
  1249.               // only add attributes with non-default values (saving a few bits)
  1250.               if (cookie.path) jscookie.path = cookie.path;
  1251.               if (cookie.name) jscookie.name = cookie.name;
  1252.               if (cookie.isSecure) jscookie.secure = true;
  1253.               if (cookie.isHttpOnly) jscookie.httponly = true;
  1254.               if (cookie.expiry < MAX_EXPIRY) jscookie.expiry = cookie.expiry;
  1255.             }
  1256.             aWindow.cookies.push(jscookie);
  1257.           }
  1258.         });
  1259.       }
  1260.     }
  1261.     
  1262.     // don't include empty cookie sections
  1263.     for (i = 0; i < aWindows.length; i++)
  1264.       if (aWindows[i].cookies.length == 0)
  1265.         delete aWindows[i].cookies;
  1266.   },
  1267.  
  1268.   /**
  1269.    * Store window dimensions, visibility, sidebar
  1270.    * @param aWindow
  1271.    *        Window reference
  1272.    */
  1273.   _updateWindowFeatures: function sss_updateWindowFeatures(aWindow) {
  1274.     var winData = this._windows[aWindow.__SSi];
  1275.     
  1276.     WINDOW_ATTRIBUTES.forEach(function(aAttr) {
  1277.       winData[aAttr] = this._getWindowDimension(aWindow, aAttr);
  1278.     }, this);
  1279.     
  1280.     var hidden = WINDOW_HIDEABLE_FEATURES.filter(function(aItem) {
  1281.       return aWindow[aItem] && !aWindow[aItem].visible;
  1282.     });
  1283.     if (hidden.length != 0)
  1284.       winData.hidden = hidden.join(",");
  1285.     else if (winData.hidden)
  1286.       delete winData.hidden;
  1287.  
  1288.     var sidebar = aWindow.document.getElementById("sidebar-box").getAttribute("sidebarcommand");
  1289.     if (sidebar)
  1290.       winData.sidebar = sidebar;
  1291.     else if (winData.sidebar)
  1292.       delete winData.sidebar;
  1293.   },
  1294.  
  1295.   /**
  1296.    * serialize session data as Ini-formatted string
  1297.    * @returns string
  1298.    */
  1299.   _getCurrentState: function sss_getCurrentState() {
  1300.     var activeWindow = this._getMostRecentBrowserWindow();
  1301.     
  1302.     if (this._loadState == STATE_RUNNING) {
  1303.       // update the data for all windows with activities since the last save operation
  1304.       this._forEachBrowserWindow(function(aWindow) {
  1305.         if (this._dirty || this._dirtyWindows[aWindow.__SSi] || aWindow == activeWindow) {
  1306.           this._collectWindowData(aWindow);
  1307.         }
  1308.         else { // always update the window features (whose change alone never triggers a save operation)
  1309.           this._updateWindowFeatures(aWindow);
  1310.         }
  1311.       }, this);
  1312.       this._dirtyWindows = [];
  1313.       this._dirty = false;
  1314.     }
  1315.     
  1316.     // collect the data for all windows
  1317.     var total = [], windows = [];
  1318.     var ix;
  1319.     for (ix in this._windows) {
  1320.       total.push(this._windows[ix]);
  1321.       windows.push(ix);
  1322.     }
  1323.     this._updateCookies(total);
  1324.     
  1325.     // if no browser window remains open, return the state of the last closed window
  1326.     if (total.length == 0 && this._lastWindowClosed) {
  1327.       total.push(this._lastWindowClosed);
  1328.     }
  1329.     if (activeWindow) {
  1330.       this.activeWindowSSiCache = activeWindow.__SSi || "";
  1331.     }
  1332.     ix = this.activeWindowSSiCache ? windows.indexOf(this.activeWindowSSiCache) : -1;
  1333.  
  1334.     return { windows: total, selectedWindow: ix + 1 };
  1335.   },
  1336.  
  1337.   /**
  1338.    * serialize session data for a window 
  1339.    * @param aWindow
  1340.    *        Window reference
  1341.    * @returns string
  1342.    */
  1343.   _getWindowState: function sss_getWindowState(aWindow) {
  1344.     if (this._loadState == STATE_RUNNING) {
  1345.       this._collectWindowData(aWindow);
  1346.     }
  1347.     
  1348.     var total = [this._windows[aWindow.__SSi]];
  1349.     this._updateCookies(total);
  1350.     
  1351.     return { windows: total };
  1352.   },
  1353.  
  1354.   _collectWindowData: function sss_collectWindowData(aWindow) {
  1355.     // update the internal state data for this window
  1356.     this._saveWindowHistory(aWindow);
  1357.     this._updateTextAndScrollData(aWindow);
  1358.     this._updateCookieHosts(aWindow);
  1359.     this._updateWindowFeatures(aWindow);
  1360.     
  1361.     this._dirtyWindows[aWindow.__SSi] = false;
  1362.   },
  1363.  
  1364. /* ........ Restoring Functionality .............. */
  1365.  
  1366.   /**
  1367.    * restore features to a single window
  1368.    * @param aWindow
  1369.    *        Window reference
  1370.    * @param aState
  1371.    *        JS object or its eval'able source
  1372.    * @param aOverwriteTabs
  1373.    *        bool overwrite existing tabs w/ new ones
  1374.    * @param aFollowUp
  1375.    *        bool this isn't the restoration of the first window
  1376.    */
  1377.   restoreWindow: function sss_restoreWindow(aWindow, aState, aOverwriteTabs, aFollowUp) {
  1378.     if (!aFollowUp) {
  1379.       this.windowToFocus = aWindow;
  1380.     }
  1381.     // initialize window if necessary
  1382.     if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
  1383.       this.onLoad(aWindow);
  1384.  
  1385.     try {
  1386.       var root = typeof aState == "string" ? this._safeEval(aState) : aState;
  1387.       if (!root.windows[0]) {
  1388.         this._notifyIfAllWindowsRestored();
  1389.         return; // nothing to restore
  1390.       }
  1391.     }
  1392.     catch (ex) { // invalid state object - don't restore anything 
  1393.       debug(ex);
  1394.       this._notifyIfAllWindowsRestored();
  1395.       return;
  1396.     }
  1397.     
  1398.     var winData;
  1399.     if (!aState.selectedWindow) {
  1400.       aState.selectedWindow = 0;
  1401.     }
  1402.     // open new windows for all further window entries of a multi-window session
  1403.     // (unless they don't contain any tab data)
  1404.     for (var w = 1; w < root.windows.length; w++) {
  1405.       winData = root.windows[w];
  1406.       if (winData && winData.tabs && winData.tabs[0]) {
  1407.         var window = this._openWindowWithState({ windows: [winData] });
  1408.         if (w == aState.selectedWindow - 1) {
  1409.           this.windowToFocus = window;
  1410.         }
  1411.       }
  1412.     }
  1413.     winData = root.windows[0];
  1414.     if (!winData.tabs) {
  1415.       winData.tabs = [];
  1416.     }
  1417.     
  1418.     var tabbrowser = aWindow.getBrowser();
  1419.     var openTabCount = aOverwriteTabs ? tabbrowser.browsers.length : -1;
  1420.     var newTabCount = winData.tabs.length;
  1421.     
  1422.     for (var t = 0; t < newTabCount; t++) {
  1423.       winData.tabs[t]._tab = t < openTabCount ? tabbrowser.mTabs[t] : tabbrowser.addTab();
  1424.       // when resuming at startup: add additionally requested pages to the end
  1425.       if (!aOverwriteTabs && root._firstTabs) {
  1426.         tabbrowser.moveTabTo(winData.tabs[t]._tab, t);
  1427.       }
  1428.     }
  1429.  
  1430.     // when overwriting tabs, remove all superflous ones
  1431.     for (t = openTabCount - 1; t >= newTabCount; t--) {
  1432.       tabbrowser.removeTab(tabbrowser.mTabs[t]);
  1433.     }
  1434.     
  1435.     if (aOverwriteTabs) {
  1436.       this.restoreWindowFeatures(aWindow, winData);
  1437.     }
  1438.     if (winData.cookies) {
  1439.       this.restoreCookies(winData.cookies);
  1440.     }
  1441.     if (winData.extData) {
  1442.       if (!this._windows[aWindow.__SSi].extData) {
  1443.         this._windows[aWindow.__SSi].extData = {}
  1444.       }
  1445.       for (var key in winData.extData) {
  1446.         this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
  1447.       }
  1448.     }
  1449.     if (winData._closedTabs && (root._firstTabs || aOverwriteTabs)) {
  1450.       this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs;
  1451.     }
  1452.     
  1453.     this.restoreHistoryPrecursor(aWindow, winData.tabs, (aOverwriteTabs ?
  1454.       (parseInt(winData.selected) || 1) : 0), 0, 0);
  1455.  
  1456.     this._notifyIfAllWindowsRestored();
  1457.   },
  1458.  
  1459.   /**
  1460.    * Manage history restoration for a window
  1461.    * @param aTabs
  1462.    *        Array of tab data
  1463.    * @param aCurrentTabs
  1464.    *        Array of tab references
  1465.    * @param aSelectTab
  1466.    *        Index of selected tab
  1467.    * @param aIx
  1468.    *        Index of the next tab to check readyness for
  1469.    * @param aCount
  1470.    *        Counter for number of times delaying b/c browser or history aren't ready
  1471.    */
  1472.   restoreHistoryPrecursor: function sss_restoreHistoryPrecursor(aWindow, aTabs, aSelectTab, aIx, aCount) {
  1473.     var tabbrowser = aWindow.getBrowser();
  1474.     
  1475.     // make sure that all browsers and their histories are available
  1476.     // - if one's not, resume this check in 100ms (repeat at most 10 times)
  1477.     for (var t = aIx; t < aTabs.length; t++) {
  1478.       try {
  1479.         if (!tabbrowser.getBrowserForTab(aTabs[t]._tab).webNavigation.sessionHistory) {
  1480.           throw new Error();
  1481.         }
  1482.       }
  1483.       catch (ex) { // in case browser or history aren't ready yet 
  1484.         if (aCount < 10) {
  1485.           var restoreHistoryFunc = function(self) {
  1486.             self.restoreHistoryPrecursor(aWindow, aTabs, aSelectTab, aIx, aCount + 1);
  1487.           }
  1488.           aWindow.setTimeout(restoreHistoryFunc, 100, this);
  1489.           return;
  1490.         }
  1491.       }
  1492.     }
  1493.     
  1494.     // mark the tabs as loading
  1495.     for (t = 0; t < aTabs.length; t++) {
  1496.       if (!aTabs[t].entries || !aTabs[t].entries[0])
  1497.         continue; // there won't be anything to load
  1498.       
  1499.       var tab = aTabs[t]._tab;
  1500.       var browser = tabbrowser.getBrowserForTab(tab);
  1501.       browser.stop(); // in case about:blank isn't done yet
  1502.       
  1503.       tab.setAttribute("busy", "true");
  1504.       tabbrowser.updateIcon(tab);
  1505.       tabbrowser.setTabTitleLoading(tab);
  1506.       
  1507.       // keep the data around to prevent dataloss in case
  1508.       // a tab gets closed before it's been properly restored
  1509.       browser.parentNode.__SS_data = aTabs[t];
  1510.     }
  1511.     
  1512.     // make sure to restore the selected tab first (if any)
  1513.     if (aSelectTab-- && aTabs[aSelectTab]) {
  1514.         aTabs.unshift(aTabs.splice(aSelectTab, 1)[0]);
  1515.         tabbrowser.selectedTab = aTabs[0]._tab;
  1516.     }
  1517.  
  1518.     // helper hash for ensuring unique frame IDs
  1519.     var idMap = { used: {} };
  1520.     this.restoreHistory(aWindow, aTabs, idMap);
  1521.   },
  1522.  
  1523.   /**
  1524.    * Restory history for a window
  1525.    * @param aWindow
  1526.    *        Window reference
  1527.    * @param aTabs
  1528.    *        Array of tab data
  1529.    * @param aIdMap
  1530.    *        Hash for ensuring unique frame IDs
  1531.    */
  1532.   restoreHistory: function sss_restoreHistory(aWindow, aTabs, aIdMap) {
  1533.     var _this = this;
  1534.     while (aTabs.length > 0 && (!aTabs[0]._tab || !aTabs[0]._tab.parentNode)) {
  1535.       aTabs.shift(); // this tab got removed before being completely restored
  1536.     }
  1537.     if (aTabs.length == 0) {
  1538.       return; // no more tabs to restore
  1539.     }
  1540.     
  1541.     var tabData = aTabs.shift();
  1542.  
  1543.     var tab = tabData._tab;
  1544.     var browser = aWindow.getBrowser().getBrowserForTab(tab);
  1545.     var history = browser.webNavigation.sessionHistory;
  1546.     
  1547.     if (history.count > 0) {
  1548.       history.PurgeHistory(history.count);
  1549.     }
  1550.     history.QueryInterface(Ci.nsISHistoryInternal);
  1551.     
  1552.     if (!tabData.entries) {
  1553.       tabData.entries = [];
  1554.     }
  1555.     if (tabData.extData) {
  1556.       tab.__SS_extdata = tabData.extData;
  1557.     }
  1558.     
  1559.     for (var i = 0; i < tabData.entries.length; i++) {
  1560.       history.addEntry(this._deserializeHistoryEntry(tabData.entries[i], aIdMap), true);
  1561.     }
  1562.     
  1563.     // make sure to reset the capabilities and attributes, in case this tab gets reused
  1564.     var disallow = (tabData.disallow)?tabData.disallow.split(","):[];
  1565.     CAPABILITIES.forEach(function(aCapability) {
  1566.       browser.docShell["allow" + aCapability] = disallow.indexOf(aCapability) == -1;
  1567.     });
  1568.     Array.filter(tab.attributes, function(aAttr) {
  1569.       return (_this.xulAttributes.indexOf(aAttr.name) > -1);
  1570.     }).forEach(tab.removeAttribute, tab);
  1571.     if (tabData.xultab) {
  1572.       tabData.xultab.split(" ").forEach(function(aAttr) {
  1573.         if (/^([^\s=]+)=(.*)/.test(aAttr)) {
  1574.           tab.setAttribute(RegExp.$1, decodeURI(RegExp.$2));
  1575.         }
  1576.       });
  1577.     }
  1578.     
  1579.     // notify the tabbrowser that the tab chrome has been restored
  1580.     var event = aWindow.document.createEvent("Events");
  1581.     event.initEvent("SSTabRestoring", true, false);
  1582.     tab.dispatchEvent(event);
  1583.     
  1584.     var activeIndex = (tabData.index || tabData.entries.length) - 1;
  1585.     try {
  1586.       browser.webNavigation.gotoIndex(activeIndex);
  1587.     }
  1588.     catch (ex) { } // ignore an invalid tabData.index
  1589.     
  1590.     // restore those aspects of the currently active documents
  1591.     // which are not preserved in the plain history entries
  1592.     // (mainly scroll state and text data)
  1593.     browser.__SS_restore_data = tabData.entries[activeIndex] || {};
  1594.     browser.__SS_restore_text = tabData.text || "";
  1595.     browser.__SS_restore_tab = tab;
  1596.     browser.__SS_restore = this.restoreDocument_proxy;
  1597.     browser.addEventListener("load", browser.__SS_restore, true);
  1598.     
  1599.     aWindow.setTimeout(function(){ _this.restoreHistory(aWindow, aTabs, aIdMap); }, 0);
  1600.   },
  1601.  
  1602.   /**
  1603.    * expands serialized history data into a session-history-entry instance
  1604.    * @param aEntry
  1605.    *        Object containing serialized history data for a URL
  1606.    * @param aIdMap
  1607.    *        Hash for ensuring unique frame IDs
  1608.    * @returns nsISHEntry
  1609.    */
  1610.   _deserializeHistoryEntry: function sss_deserializeHistoryEntry(aEntry, aIdMap) {
  1611.     var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
  1612.                   createInstance(Ci.nsISHEntry);
  1613.     
  1614.     var ioService = Cc["@mozilla.org/network/io-service;1"].
  1615.                     getService(Ci.nsIIOService);
  1616.     shEntry.setURI(ioService.newURI(aEntry.url, null, null));
  1617.     shEntry.setTitle(aEntry.title || aEntry.url);
  1618.     if (aEntry.subframe)
  1619.       shEntry.setIsSubFrame(aEntry.subframe || false);
  1620.     shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
  1621.     if (aEntry.contentType)
  1622.       shEntry.contentType = aEntry.contentType;
  1623.     
  1624.     if (aEntry.cacheKey) {
  1625.       var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
  1626.                      createInstance(Ci.nsISupportsPRUint32);
  1627.       cacheKey.data = aEntry.cacheKey;
  1628.       shEntry.cacheKey = cacheKey;
  1629.     }
  1630.  
  1631.     if (aEntry.ID) {
  1632.       // get a new unique ID for this frame (since the one from the last
  1633.       // start might already be in use)
  1634.       var id = aIdMap[aEntry.ID] || 0;
  1635.       if (!id) {
  1636.         for (id = Date.now(); aIdMap.used[id]; id++);
  1637.         aIdMap[aEntry.ID] = id;
  1638.         aIdMap.used[id] = true;
  1639.       }
  1640.       shEntry.ID = id;
  1641.     }
  1642.     
  1643.     if (aEntry.scroll) {
  1644.       var scrollPos = (aEntry.scroll || "0,0").split(",");
  1645.       scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
  1646.       shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
  1647.     }
  1648.  
  1649.     var postdata;
  1650.     if (aEntry.postdata_b64) {  // Firefox 3
  1651.       postdata = atob(aEntry.postdata_b64);
  1652.     } else if (aEntry.postdata) { // Firefox 2
  1653.       postdata = aEntry.postdata;
  1654.     }
  1655.  
  1656.     if (postdata) {
  1657.       var stream = Cc["@mozilla.org/io/string-input-stream;1"].
  1658.                    createInstance(Ci.nsIStringInputStream);
  1659.       stream.setData(postdata, postdata.length);
  1660.       shEntry.postData = stream;
  1661.     }
  1662.  
  1663.     if (aEntry.owner_b64) {  // Firefox 3
  1664.       var ownerInput = Cc["@mozilla.org/io/string-input-stream;1"].
  1665.                        createInstance(Ci.nsIStringInputStream);
  1666.       var binaryData = atob(aEntry.owner_b64);
  1667.       ownerInput.setData(binaryData, binaryData.length);
  1668.       var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
  1669.                          createInstance(Ci.nsIObjectInputStream);
  1670.       binaryStream.setInputStream(ownerInput);
  1671.       try { // Catch possible deserialization exceptions
  1672.         shEntry.owner = binaryStream.readObject(true);
  1673.       } catch (ex) { debug(ex); }
  1674.     } else if (aEntry.ownerURI) { // Firefox 2
  1675.       var uriObj = ioService.newURI(aEntry.ownerURI, null, null);
  1676.       shEntry.owner = Cc["@mozilla.org/scriptsecuritymanager;1"].
  1677.                       getService(Ci.nsIScriptSecurityManager).
  1678.                       getCodebasePrincipal(uriObj);
  1679.     }
  1680.     
  1681.     if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
  1682.       for (var i = 0; i < aEntry.children.length; i++) {
  1683.         shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap), i);
  1684.       }
  1685.     }
  1686.     
  1687.     return shEntry;
  1688.   },
  1689.  
  1690.   /**
  1691.    * Restore properties to a loaded document
  1692.    */
  1693.   restoreDocument_proxy: function sss_restoreDocument_proxy(aEvent) {
  1694.     // wait for the top frame to be loaded completely
  1695.     if (!aEvent || !aEvent.originalTarget || !aEvent.originalTarget.defaultView || aEvent.originalTarget.defaultView != aEvent.originalTarget.defaultView.top) {
  1696.       return;
  1697.     }
  1698.     
  1699.     // XSS note: always call this before injecting content into a document!
  1700.     function hasExpectedURL(aDocument, aURL)
  1701.       !aURL || aURL.replace(/#.*/, "") == aDocument.location.href.replace(/#.*/, "");
  1702.     
  1703.     var textArray = this.__SS_restore_text ? this.__SS_restore_text.split(" ") : [];
  1704.     function restoreTextData(aContent, aPrefix, aURL) {
  1705.       textArray.forEach(function(aEntry) {
  1706.         if (/^((?:\d+\|)*)(#?)([^\s=]+)=(.*)$/.test(aEntry) &&
  1707.             RegExp.$1 == aPrefix && hasExpectedURL(aContent.document, aURL)) {
  1708.           var document = aContent.document;
  1709.           var node = RegExp.$2 ? document.getElementById(RegExp.$3) : document.getElementsByName(RegExp.$3)[0] || null;
  1710.           if (node && "value" in node && node.type != "file") {
  1711.             node.value = decodeURI(RegExp.$4);
  1712.             
  1713.             var event = document.createEvent("UIEvents");
  1714.             event.initUIEvent("input", true, true, aContent, 0);
  1715.             node.dispatchEvent(event);
  1716.           }
  1717.         }
  1718.       });
  1719.     }
  1720.     
  1721.     let window = this.ownerDocument.defaultView;
  1722.     function restoreTextDataAndScrolling(aContent, aData, aPrefix) {
  1723.       restoreTextData(aContent, aPrefix, aData.url);
  1724.       if (aData.innerHTML) {
  1725.         window.setTimeout(function() {
  1726.           if (aContent.document.designMode == "on" &&
  1727.               hasExpectedURL(aContent.document, aData.url)) {
  1728.             aContent.document.body.innerHTML = aData.innerHTML;
  1729.           }
  1730.         }, 0);
  1731.       }
  1732.       if (aData.scroll && /(\d+),(\d+)/.test(aData.scroll)) {
  1733.         aContent.scrollTo(RegExp.$1, RegExp.$2);
  1734.       }
  1735.       for (var i = 0; i < aContent.frames.length; i++) {
  1736.         if (aData.children && aData.children[i] &&
  1737.           hasExpectedURL(aContent.document, aData.url)) {
  1738.           restoreTextDataAndScrolling(aContent.frames[i], aData.children[i], aPrefix + i + "|");
  1739.         }
  1740.       }
  1741.     }
  1742.     
  1743.     if (hasExpectedURL(aEvent.originalTarget, this.__SS_restore_data.url)) {
  1744.       var content = aEvent.originalTarget.defaultView;
  1745.       if (this.currentURI.spec == "about:config") {
  1746.         // unwrap the document for about:config because otherwise the properties
  1747.         // of the XBL bindings - as the textbox - aren't accessible (see bug 350718)
  1748.         content = content.wrappedJSObject;
  1749.       }
  1750.       restoreTextDataAndScrolling(content, this.__SS_restore_data, "");
  1751.     }
  1752.     
  1753.     // notify the tabbrowser that this document has been completely restored
  1754.     var event = this.ownerDocument.createEvent("Events");
  1755.     event.initEvent("SSTabRestored", true, false);
  1756.     this.__SS_restore_tab.dispatchEvent(event);
  1757.     
  1758.     this.removeEventListener("load", this.__SS_restore, true);
  1759.     delete this.__SS_restore_data;
  1760.     delete this.__SS_restore_text;
  1761.     delete this.__SS_restore_tab;
  1762.     delete this.__SS_restore;
  1763.   },
  1764.  
  1765.   /**
  1766.    * Restore visibility and dimension features to a window
  1767.    * @param aWindow
  1768.    *        Window reference
  1769.    * @param aWinData
  1770.    *        Object containing session data for the window
  1771.    */
  1772.   restoreWindowFeatures: function sss_restoreWindowFeatures(aWindow, aWinData) {
  1773.     var hidden = (aWinData.hidden)?aWinData.hidden.split(","):[];
  1774.     WINDOW_HIDEABLE_FEATURES.forEach(function(aItem) {
  1775.       aWindow[aItem].visible = hidden.indexOf(aItem) == -1;
  1776.     });
  1777.     
  1778.     var _this = this;
  1779.     aWindow.setTimeout(function() {
  1780.       _this.restoreDimensions.apply(_this, [aWindow, aWinData.width || 0, 
  1781.         aWinData.height || 0, "screenX" in aWinData ? aWinData.screenX : NaN,
  1782.         "screenY" in aWinData ? aWinData.screenY : NaN,
  1783.         aWinData.sizemode || "", aWinData.sidebar || ""]);
  1784.     }, 0);
  1785.   },
  1786.  
  1787.   /**
  1788.    * Restore a window's dimensions
  1789.    * @param aWidth
  1790.    *        Window width
  1791.    * @param aHeight
  1792.    *        Window height
  1793.    * @param aLeft
  1794.    *        Window left
  1795.    * @param aTop
  1796.    *        Window top
  1797.    * @param aSizeMode
  1798.    *        Window size mode (eg: maximized)
  1799.    * @param aSidebar
  1800.    *        Sidebar command
  1801.    */
  1802.   restoreDimensions: function sss_restoreDimensions(aWindow, aWidth, aHeight, aLeft, aTop, aSizeMode, aSidebar) {
  1803.     var win = aWindow;
  1804.     var _this = this;
  1805.     function win_(aName) { return _this._getWindowDimension(win, aName); }
  1806.     
  1807.     // only modify those aspects which aren't correct yet
  1808.     if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) {
  1809.       aWindow.resizeTo(aWidth, aHeight);
  1810.     }
  1811.     if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
  1812.       aWindow.moveTo(aLeft, aTop);
  1813.     }
  1814.     if (aSizeMode == "maximized" && win_("sizemode") != "maximized") {
  1815.       aWindow.maximize();
  1816.     }
  1817.     else if (aSizeMode && aSizeMode != "maximized" && win_("sizemode") != "normal") {
  1818.       aWindow.restore();
  1819.     }
  1820.     var sidebar = aWindow.document.getElementById("sidebar-box");
  1821.     if (sidebar.getAttribute("sidebarcommand") != aSidebar) {
  1822.       aWindow.toggleSidebar(aSidebar);
  1823.     }
  1824.     // since resizing/moving a window brings it to the foreground,
  1825.     // we might want to re-focus the last focused window
  1826.     if (this.windowToFocus) {
  1827.       this.windowToFocus.content.focus();
  1828.     }
  1829.   },
  1830.  
  1831.   /**
  1832.    * Restores cookies (accepting both Firefox 2.0 and current format)
  1833.    * @param aCookies
  1834.    *        Array of cookie objects
  1835.    */
  1836.   restoreCookies: function sss_restoreCookies(aCookies) {
  1837.     if (aCookies.count && aCookies.domain1) {
  1838.       // convert to the new cookie serialization format
  1839.       var converted = [];
  1840.       for (var i = 1; i <= aCookies.count; i++) {
  1841.         // for simplicity we only accept the format we produced ourselves
  1842.         var parsed = aCookies["value" + i].match(/^([^=;]+)=([^;]*);(?:domain=[^;]+;)?(?:path=([^;]*);)?(secure;)?(httponly;)?/);
  1843.         if (parsed && /^https?:\/\/([^\/]+)/.test(aCookies["domain" + i]))
  1844.           converted.push({
  1845.             host: RegExp.$1, path: parsed[3], name: parsed[1], value: parsed[2],
  1846.             secure: parsed[4], httponly: parsed[5]
  1847.           });
  1848.       }
  1849.       aCookies = converted;
  1850.     }
  1851.     
  1852.     var cookieManager = Cc["@mozilla.org/cookiemanager;1"].
  1853.                         getService(Ci.nsICookieManager2);
  1854.     // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
  1855.     var MAX_EXPIRY = Math.pow(2, 62);
  1856.     for (i = 0; i < aCookies.length; i++) {
  1857.       var cookie = aCookies[i];
  1858.       try {
  1859.         cookieManager.add(cookie.host, cookie.path || "", cookie.name || "", cookie.value, !!cookie.secure, !!cookie.httponly, true, "expiry" in cookie ? cookie.expiry : MAX_EXPIRY);
  1860.       }
  1861.       catch (ex) { Cu.reportError(ex); } // don't let a single cookie stop recovering
  1862.     }
  1863.   },
  1864.  
  1865. /* ........ Disk Access .............. */
  1866.  
  1867.   /**
  1868.    * save state delayed by N ms
  1869.    * marks window as dirty (i.e. data update can't be skipped)
  1870.    * @param aWindow
  1871.    *        Window reference
  1872.    * @param aDelay
  1873.    *        Milliseconds to delay
  1874.    */
  1875.   saveStateDelayed: function sss_saveStateDelayed(aWindow, aDelay) {
  1876.     if (aWindow) {
  1877.       this._dirtyWindows[aWindow.__SSi] = true;
  1878.     }
  1879.  
  1880.     if (!this._saveTimer && this._resume_from_crash) {
  1881.       // interval until the next disk operation is allowed
  1882.       var minimalDelay = this._lastSaveTime + this._interval - Date.now();
  1883.       
  1884.       // if we have to wait, set a timer, otherwise saveState directly
  1885.       aDelay = Math.max(minimalDelay, aDelay || 2000);
  1886.       if (aDelay > 0) {
  1887.         this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1888.         this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
  1889.       }
  1890.       else {
  1891.         this.saveState();
  1892.       }
  1893.     }
  1894.   },
  1895.  
  1896.   /**
  1897.    * save state to disk
  1898.    * @param aUpdateAll
  1899.    *        Bool update all windows 
  1900.    */
  1901.   saveState: function sss_saveState(aUpdateAll) {
  1902.     // if crash recovery is disabled, only save session resuming information
  1903.     if (!this._resume_from_crash && this._loadState == STATE_RUNNING)
  1904.       return;
  1905.     
  1906.     this._dirty = aUpdateAll;
  1907.     var oState = this._getCurrentState();
  1908.     oState.session = { state: ((this._loadState == STATE_RUNNING) ? STATE_RUNNING_STR : STATE_STOPPED_STR) };
  1909.     this._writeFile(this._sessionFile, oState.toSource());
  1910.     this._lastSaveTime = Date.now();
  1911.   },
  1912.  
  1913.   /**
  1914.    * delete session datafile and backup
  1915.    */
  1916.   _clearDisk: function sss_clearDisk() {
  1917.     if (this._sessionFile.exists()) {
  1918.       try {
  1919.         this._sessionFile.remove(false);
  1920.       }
  1921.       catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
  1922.     }
  1923.     if (this._sessionFileBackup.exists()) {
  1924.       try {
  1925.         this._sessionFileBackup.remove(false);
  1926.       }
  1927.       catch (ex) { dump(ex + '\n'); } // couldn't remove the file - what now?
  1928.     }
  1929.   },
  1930.  
  1931. /* ........ Auxiliary Functions .............. */
  1932.  
  1933.   /**
  1934.    * call a callback for all currently opened browser windows
  1935.    * (might miss the most recent one)
  1936.    * @param aFunc
  1937.    *        Callback each window is passed to
  1938.    */
  1939.   _forEachBrowserWindow: function sss_forEachBrowserWindow(aFunc) {
  1940.     var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
  1941.                          getService(Ci.nsIWindowMediator);
  1942.     var windowsEnum = windowMediator.getEnumerator("navigator:browser");
  1943.     
  1944.     while (windowsEnum.hasMoreElements()) {
  1945.       var window = windowsEnum.getNext();
  1946.       if (window.__SSi) {
  1947.         aFunc.call(this, window);
  1948.       }
  1949.     }
  1950.   },
  1951.  
  1952.   /**
  1953.    * Returns most recent window
  1954.    * @returns Window reference
  1955.    */
  1956.   _getMostRecentBrowserWindow: function sss_getMostRecentBrowserWindow() {
  1957.     var windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
  1958.                          getService(Ci.nsIWindowMediator);
  1959.     return windowMediator.getMostRecentWindow("navigator:browser");
  1960.   },
  1961.  
  1962.   /**
  1963.    * open a new browser window for a given session state
  1964.    * called when restoring a multi-window session
  1965.    * @param aState
  1966.    *        Object containing session data
  1967.    */
  1968.   _openWindowWithState: function sss_openWindowWithState(aState) {
  1969.     var argString = Cc["@mozilla.org/supports-string;1"].
  1970.                     createInstance(Ci.nsISupportsString);
  1971.     argString.data = "";
  1972.  
  1973.     //XXXzeniko shouldn't it be possible to set the window's dimensions here (as feature)?
  1974.     var window = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  1975.                  getService(Ci.nsIWindowWatcher).
  1976.                  openWindow(null, this._prefBranch.getCharPref("chromeURL"), "_blank",
  1977.                             "chrome,dialog=no,all", argString);
  1978.     
  1979.     window.__SS_state = aState;
  1980.     var _this = this;
  1981.     window.addEventListener("load", function(aEvent) {
  1982.       aEvent.currentTarget.removeEventListener("load", arguments.callee, true);
  1983.       _this.restoreWindow(aEvent.currentTarget, aEvent.currentTarget.__SS_state, true, true);
  1984.       delete aEvent.currentTarget.__SS_state;
  1985.     }, true);
  1986.     
  1987.     return window;
  1988.   },
  1989.  
  1990.   /**
  1991.    * Whether or not to resume session, if not recovering from a crash.
  1992.    * @returns bool
  1993.    */
  1994.   _doResumeSession: function sss_doResumeSession() {
  1995.     return this._prefBranch.getIntPref("startup.page") == 3 ||
  1996.       this._prefBranch.getBoolPref("sessionstore.resume_session_once");
  1997.   },
  1998.  
  1999.   /**
  2000.    * whether the user wants to load any other page at startup
  2001.    * (except the homepage) - needed for determining whether to overwrite the current tabs
  2002.    * C.f.: nsBrowserContentHandler's defaultArgs implementation.
  2003.    * @returns bool
  2004.    */
  2005.   _isCmdLineEmpty: function sss_isCmdLineEmpty(aWindow) {
  2006.     var defaultArgs = Cc["@mozilla.org/browser/clh;1"].
  2007.                       getService(Ci.nsIBrowserHandler).defaultArgs;
  2008.     if (aWindow.arguments && aWindow.arguments[0] &&
  2009.         aWindow.arguments[0] == defaultArgs)
  2010.       aWindow.arguments[0] = null;
  2011.  
  2012.     return !aWindow.arguments || !aWindow.arguments[0];
  2013.   },
  2014.  
  2015.   /**
  2016.    * don't save sensitive data if the user doesn't want to
  2017.    * (distinguishes between encrypted and non-encrypted sites)
  2018.    * @param aIsHTTPS
  2019.    *        Bool is encrypted
  2020.    * @returns bool
  2021.    */
  2022.   _checkPrivacyLevel: function sss_checkPrivacyLevel(aIsHTTPS) {
  2023.     return this._prefBranch.getIntPref("sessionstore.privacy_level") < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
  2024.   },
  2025.  
  2026.   /**
  2027.    * on popup windows, the XULWindow's attributes seem not to be set correctly
  2028.    * we use thus JSDOMWindow attributes for sizemode and normal window attributes
  2029.    * (and hope for reasonable values when maximized/minimized - since then
  2030.    * outerWidth/outerHeight aren't the dimensions of the restored window)
  2031.    * @param aWindow
  2032.    *        Window reference
  2033.    * @param aAttribute
  2034.    *        String sizemode | width | height | other window attribute
  2035.    * @returns string
  2036.    */
  2037.   _getWindowDimension: function sss_getWindowDimension(aWindow, aAttribute) {
  2038.     if (aAttribute == "sizemode") {
  2039.       switch (aWindow.windowState) {
  2040.       case aWindow.STATE_MAXIMIZED:
  2041.         return "maximized";
  2042.       case aWindow.STATE_MINIMIZED:
  2043.         return "minimized";
  2044.       default:
  2045.         return "normal";
  2046.       }
  2047.     }
  2048.     
  2049.     var dimension;
  2050.     switch (aAttribute) {
  2051.     case "width":
  2052.       dimension = aWindow.outerWidth;
  2053.       break;
  2054.     case "height":
  2055.       dimension = aWindow.outerHeight;
  2056.       break;
  2057.     default:
  2058.       dimension = aAttribute in aWindow ? aWindow[aAttribute] : "";
  2059.       break;
  2060.     }
  2061.     
  2062.     if (aWindow.windowState == aWindow.STATE_NORMAL) {
  2063.       return dimension;
  2064.     }
  2065.     return aWindow.document.documentElement.getAttribute(aAttribute) || dimension;
  2066.   },
  2067.  
  2068.   /**
  2069.    * Convenience method to get localized string bundles
  2070.    * @param aURI
  2071.    * @returns nsIStringBundle
  2072.    */
  2073.   _getStringBundle: function sss_getStringBundle(aURI) {
  2074.      var bundleService = Cc["@mozilla.org/intl/stringbundle;1"].
  2075.                          getService(Ci.nsIStringBundleService);
  2076.      var appLocale = Cc["@mozilla.org/intl/nslocaleservice;1"].
  2077.                      getService(Ci.nsILocaleService).getApplicationLocale();
  2078.      return bundleService.createBundle(aURI, appLocale);
  2079.   },
  2080.  
  2081.   /**
  2082.    * Get nsIURI from string
  2083.    * @param string
  2084.    * @returns nsIURI
  2085.    */
  2086.   _getURIFromString: function sss_getURIFromString(aString) {
  2087.     var ioService = Cc["@mozilla.org/network/io-service;1"].
  2088.                     getService(Ci.nsIIOService);
  2089.     return ioService.newURI(aString, null, null);
  2090.   },
  2091.  
  2092.   /**
  2093.    * Annotate a breakpad crash report with the currently selected tab's URL.
  2094.    */
  2095.   _updateCrashReportURL: function sss_updateCrashReportURL(aWindow) {
  2096.     if (!Ci.nsICrashReporter) {
  2097.       // if breakpad isn't built, don't bother next time at all
  2098.       this._updateCrashReportURL = function(aWindow) {};
  2099.       return;
  2100.     }
  2101.     try {
  2102.       var currentUrl = aWindow.getBrowser().currentURI.spec;
  2103.       var cr = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsICrashReporter);
  2104.       cr.annotateCrashReport("URL", currentUrl);
  2105.     }
  2106.     catch (ex) {
  2107.       // don't make noise when crashreporter is built but not enabled
  2108.       if (ex.result != Components.results.NS_ERROR_NOT_INITIALIZED)
  2109.         debug(ex);
  2110.     }
  2111.   },
  2112.  
  2113.   /**
  2114.    * safe eval'ing
  2115.    */
  2116.   _safeEval: function sss_safeEval(aStr) {
  2117.     return Cu.evalInSandbox(aStr, new Cu.Sandbox("about:blank"));
  2118.   },
  2119.  
  2120.   /**
  2121.    * Converts a JavaScript object into a JSON string
  2122.    * (see http://www.json.org/ for more information).
  2123.    *
  2124.    * The inverse operation consists of eval("(" + JSON_string + ")");
  2125.    * and should be provably safe.
  2126.    *
  2127.    * @param aJSObject is the object to be converted
  2128.    * @return the object's JSON representation
  2129.    */
  2130.   _toJSONString: function sss_toJSONString(aJSObject) {
  2131.     var str = JSON.toString(aJSObject, ["_tab", "_hosts"] /* keys to drop */);
  2132.     
  2133.     // sanity check - so that API consumers can just eval this string
  2134.     if (!JSON.isMostlyHarmless(str))
  2135.       throw new Error("JSON conversion failed unexpectedly!");
  2136.     
  2137.     return str;
  2138.   },
  2139.  
  2140.   _notifyIfAllWindowsRestored: function sss_notifyIfAllWindowsRestored() {
  2141.     if (this._restoreCount) {
  2142.       this._restoreCount--;
  2143.       if (this._restoreCount == 0) {
  2144.         // This was the last window restored at startup, notify observers.
  2145.         var observerService = Cc["@mozilla.org/observer-service;1"].
  2146.                               getService(Ci.nsIObserverService);
  2147.         observerService.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
  2148.       }
  2149.     }
  2150.   },
  2151.  
  2152.  
  2153. /* ........ Storage API .............. */
  2154.  
  2155.   /**
  2156.    * write file to disk
  2157.    * @param aFile
  2158.    *        nsIFile
  2159.    * @param aData
  2160.    *        String data
  2161.    */
  2162.   _writeFile: function sss_writeFile(aFile, aData) {
  2163.     // init stream
  2164.     var stream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  2165.                  createInstance(Ci.nsIFileOutputStream);
  2166.     stream.init(aFile, 0x02 | 0x08 | 0x20, 0600, 0);
  2167.  
  2168.     // convert to UTF-8
  2169.     var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  2170.                     createInstance(Ci.nsIScriptableUnicodeConverter);
  2171.     converter.charset = "UTF-8";
  2172.     var convertedData = converter.ConvertFromUnicode(aData);
  2173.     convertedData += converter.Finish();
  2174.  
  2175.     // write and close stream
  2176.     stream.write(convertedData, convertedData.length);
  2177.     if (stream instanceof Ci.nsISafeOutputStream) {
  2178.       stream.finish();
  2179.     } else {
  2180.       stream.close();
  2181.     }
  2182.   }
  2183. };
  2184.  
  2185. function NSGetModule(aComMgr, aFileSpec)
  2186.   XPCOMUtils.generateModule([SessionStoreService]);
  2187.